package com.bitcola.chainserver.chain.trx.service;


import com.bitcola.chainserver.chain.trx.api.ContractEvent;
import com.bitcola.chainserver.chain.trx.api.Record;
import com.bitcola.chainserver.chain.trx.api.TRXTransform;
import com.bitcola.chainserver.chain.trx.config.Constant;
import com.bitcola.chainserver.chain.trx.core.Credential;
import com.bitcola.chainserver.chain.trx.core.Trc20;
import com.bitcola.chainserver.chain.trx.core.TronApi;
import com.bitcola.chainserver.chain.trx.core.TronKit;
import com.google.gson.Gson;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Service
public class TransactionScanner implements ApplicationRunner {

    ExecutorService executors = Executors.newFixedThreadPool(300);

    Gson gson = new Gson();
    volatile static long lastBlockNumber = 0;



    public static final int confirmNumber=15;

    public long getLastBlockNumber()
    {
        return lastBlockNumber;
}

    public void updateLastBlockNumber() {
        executors.submit(() -> {
            var tronAPi = new TronApi();
            while (true) {
                try {
                    lastBlockNumber = tronAPi.getLastNlock();
                    Thread.sleep(1000);
                } catch (Exception e) {

                }
            }
        });
    }

    Map<String, String> address = new ConcurrentHashMap<>();
    Map<String, Record> unConfirm = new ConcurrentHashMap<>();
    BlockingQueue<Long> unSuccessNumber = new LinkedBlockingQueue<>();
    Map<String, String> recordMap = new ConcurrentHashMap<>();

    List<String> listenedContractAddress = new CopyOnWriteArrayList<>();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        initListenedAddress();
        initRecordMap();
        initUnConfirm();
        updateLastBlockNumber();
//        scanTrxTransaction();
//        scanTokenTansaction();
//        scanError();
        return;

    }

    public void initUnConfirm() {
//        long size = redisTemplate.opsForHash().size(Constant.UN_CONFIRM_QUEUE);
//        if (size > 0) {
//            var transactionsKey = redisTemplate.opsForHash().keys(Constant.UN_CONFIRM_QUEUE);
//            for (Object a : transactionsKey) {
//                String key = a.toString();
//                var tran = redisTemplate.opsForHash().get(Constant.UN_CONFIRM_QUEUE, key);
//                unConfirm.put(key,  gson.fromJson(tran.toString(),Record.class));
//            }
//        }
    }

    public void initListenedAddress() {

//        long size = redisTemplate.opsForList().size(Constant.LISTENED_ADDRESS);
//        if (size > 0) {
//            var address = redisTemplate.opsForList().range(Constant.LISTENED_ADDRESS, 0, size);
//            for (Object a : address) {
//                listenedContractAddress.add(a.toString());
//            }
//        }
    }

    public void initRecordMap() {
//        long size = redisTemplate.opsForHash().size(Constant.RECORD_MAP);
//        if (size > 0) {
//            var hashs = redisTemplate.opsForHash().keys(Constant.RECORD_MAP);
//            for (Object key : hashs) {
//                recordMap.put(key.toString(), key.toString());
//            }
//        }


    }


    public void addAddress(List<String> address) {
        for (String a : address) {
            this.address.put(a, a);
//            redisTemplate.opsForList().rightPush(Constant.LISTENED_ADDRESS, a);
        }
    }

    public void addContractAddress(List<String> address) {
        listenedContractAddress.addAll(address);
    }

    private void scanTokenTansaction() throws Exception {
        var tronAPi = new TronApi();
        long currentNum = lastBlockNumber;
        while (true) {
            tronAPi = new TronApi();
            try {


                var api = lastBlockNumber;
                if (currentNum > 0) {
                    while (api > currentNum) {

                        System.out.println("scan: " + currentNum + "-----------------------");
                        scanTokenByNumber(tronAPi, currentNum);
                        currentNum++;
                    }
                } else {
                    currentNum = lastBlockNumber;
                }

                Thread.sleep(1000);
            } catch (Exception e) {
                unSuccessNumber.add(currentNum);
                e.printStackTrace();
            }

        }


    }

    public void addNewRecord(String txid, String toAddress, String contractAddress, BigDecimal amount, long blockNumber, long currentBlockNumber) throws Exception {
        TronKit kit = new TronKit(
                TronApi.mainNet(),
                Credential.fromPrivateKey("8D9142B97B38F992B4ADF9FB3D0DD527B1F47BE113C6D0B5C32A0571EF1E7B5F")
        );
        Trc20 token = kit.trc20(contractAddress);

        Record record = new Record();
        record.txid = txid;
        record.address = toAddress;
        record.contractAddress = contractAddress;
        record.bockNumber = blockNumber;
        record.currentBockNumber = currentBlockNumber;
        if(!StringUtils.isEmpty(contractAddress)) {
            var decimal = token.decimals();
            amount = amount.divide(BigDecimal.valueOf(decimal.longValue()),10, RoundingMode.DOWN);
        }else{
            amount=amount.divide(BigDecimal.valueOf(Constant.SUN),10,RoundingMode.DOWN);
        }
        record.amount = amount;
        unConfirm.put(txid, record);
//        redisTemplate.opsForHash().put(Constant.UN_CONFIRM_QUEUE, txid, record);
    }

    public void recordSuccess(Record record)
    {

//        redisTemplate.opsForList().rightPush(Constant.ONFIRM_QUEUE,record);
    }


    public void recordNumber(long number) {
        for (String key : unConfirm.keySet()) {
            var record = unConfirm.get(key);
            record.currentBockNumber = number;
            if(record.currentBockNumber-record.bockNumber>15){
                recordSuccess(record);
//                redisTemplate.opsForHash().delete(Constant.UN_CONFIRM_QUEUE,key);
            }
        }
    }


    private void scanTokenByNumber(TronApi tronAPi, long currentNum) throws Exception {
        var list = tronAPi.getBlockEvents(currentNum);
        list = list.stream().filter(x -> x.eventName.equals("Transfer")).collect(Collectors.toList());
        scanTokenEvent(list);
    }

    private void scanTokenEvent(List<ContractEvent> events) {
        for (ContractEvent event : events) {

            event.resultType.forEach((k, v) -> {
                        if (k.equals("to")) {
                            String toAddress = event.result.get(k).toString();
                            if (listenedContractAddress.contains(toAddress)) {
                                BigDecimal amount = new BigDecimal(event.result.get("amount").toString());

                                try {
                                    addNewRecord(event.transactionId,toAddress,event.contractAddress,amount,event.blockNumber,event.blockNumber);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }


                            }

                        }
                    }
            );
        }

    }

    private void scanTrxTransaction() {
        executors.submit(() -> {
            var tronAPi = new TronApi();
            long currentNum = lastBlockNumber;
            while (true) {
                tronAPi = new TronApi();
                try {


                    var api = lastBlockNumber;
                    if (currentNum > 0) {
                        while (api > currentNum && currentNum > 0) {
                            recordNumber(currentNum);
                            System.out.println("scan: " + currentNum + "-----------------------");
                            scanTransactionByNumber(tronAPi, currentNum);
                            currentNum++;
                        }
                    } else {
                        currentNum = lastBlockNumber;
                    }

                    Thread.sleep(1000);
                } catch (Exception e) {
                    unSuccessNumber.add(currentNum);
                    e.printStackTrace();
                }

            }
        });

    }

    private void scanTransactionByNumber(TronApi tronAPi, long currentNum) throws Exception {
//        var list = tronAPi.getTransformsByBlockNumber(currentNum);
//        scanBlock(list);
    }

    public void scanError() {

        executors.submit(() -> {
            while (true) {
                long temp = 0;
                try {
                    Thread.sleep(6000);
                    var tronAPi = new TronApi();
                    long currentNum = unSuccessNumber.take();
                    System.out.println("扫描错误区块:" + currentNum);
                    scanTransactionByNumber(tronAPi, currentNum);
                    scanTokenByNumber(tronAPi, currentNum);
                    temp = currentNum;

                } catch (Exception e) {
                    unSuccessNumber.add(temp);
                    e.printStackTrace();
                }

            }
        });


    }


    public void scanBlock(List<TRXTransform> transforms) {
        for (TRXTransform trxTransform : transforms) {
            if (address.containsKey(trxTransform.to)) {
                try {
                    addNewRecord(trxTransform.txid,trxTransform.to,null,trxTransform.value,trxTransform.block_no,trxTransform.block_no);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    }
}
